/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.etherframework.wire.wirings
{
	import org.etherframework.core.Access;
	import org.etherframework.core.DomainAccess;
	import org.etherframework.core.IBridge;
	import org.etherframework.core.events.InteractionEvent;
	import org.etherframework.reflection.Accessor;
	import org.etherframework.reflection.Metadata;
	import org.etherframework.reflection.MetadataMember;
	import org.etherframework.reflection.Variable;
	import org.etherframework.wire.WiringData;
	import org.etherframework.wire.WiringUtil;
	
	import flash.events.Event;
	import flash.utils.Dictionary;
	import mx.events.PropertyChangeEvent;
	import mx.binding.utils.ChangeWatcher;
	
	public class Autowire implements IWiring, IInjection
	{
		[Inspectable(defaultValue="local", enumeration="local,hierarchical,@hierarchical,hierarchical@,global")]
		public var access:String = "local";
		
		[Inspectable(defaultValue="single", enumeration="single,multiple")]
		public var domain:String = "single";
		
		public var proxy:String;
		
		public var bean:String;
		
		public var value:*;
		
		public var member:Object;
		
		public var lazy:Boolean = false;
		
		// implemented only for beans
		public var bindable:Boolean = false;
		
		// to be implemented
		public var strong:Boolean = false;
		
		private var dictionary:Dictionary;
		
		private var itemName:String;
		
		private var bridge:IBridge;
		
		private var changeWatcher:ChangeWatcher;
		
		public function wire(name:String, target:Object, item:Object, data:WiringData):Boolean
		{
			if (!((item is Variable) || (item is Accessor)))
				return false;
			
			var j:int;
			var member:MetadataMember = item as MetadataMember;
			var metadatas:Array;
			var metadatasLen:int;
			var metadata:Metadata;
			var properties:Dictionary;
			
			metadatas = member.metadata;
			metadatasLen = metadatas.length;
			this.member = (member.uri) ? 
						new QName(new Namespace(member.uri), member.name) : member.name;
			bridge = data.bridge;
			
			try {
				for (var i:int = 0; i < metadatasLen; ++i)
				{
					metadata = metadatas[i];
					
					if (metadata.name == name)
					{
						properties = WiringUtil.getProperties(metadata.properties);
						access = Access.getAccess(properties["access"]);
						domain = DomainAccess.getAccess(properties["domain"]);
						bean = properties["bean"];
						proxy = properties["proxy"];
						lazy = (properties["lazy"] == "true");
						bindable = (properties["bindable"] == "true");
						strong = (properties["strong"] == "true");
						
						if (bean)
						{
							value = wireBean(target, bean, this.member, bridge);
						}
						else if (proxy)
						{
							value = wireProxy(target, proxy, this.member, bridge);
						}
						
						break;
					}
				}
			}catch(e:Error) {trace("ERROR");}
			
			return Boolean(bean || proxy);
		}
		
		public function unwire(target:Object):Boolean
		{
			if (changeWatcher) changeWatcher.unwatch();
			dictionary = null;
			
			try {
				if (target[member])
				{
					target[member] = null;
					return true;
				}
			} catch (e:*) {}
			
			return false;
		}
		
		public function inject(name:String, target:Object, data:WiringData):Boolean
		{
			try {
				if (!target[member])
				{
					access = Access.getAccess(access);
					domain = DomainAccess.getAccess(domain);
					bridge = data.bridge;
					
					if (bean)
					{
						value = wireBean(target, bean, member, bridge);
					}
					else if (proxy)
					{
						value = wireProxy(target, proxy, member, bridge);
					}
				}
			} catch (e:*) {}
			
			return Boolean(value);
		}
		
		private function wireBean(target:Object, bean:String, member:Object, bridge:IBridge, beanInfo:Object = null):*
		{
			var objValue:*;
			var objProperties:Array = bean.split(".");
			itemName = objProperties.shift();
			
			if (!beanInfo)
				beanInfo = bridge.getBean(itemName);
			
			if (!beanInfo && (access != Access.LOCAL))
			{
				var evt:InteractionEvent = new InteractionEvent(InteractionEvent.GET_DEPENDENCY + itemName);
				evt.data = itemName;
				bridge.dispatch(evt, access, domain);
				beanInfo = evt.result;
			}
			
			dictionary = new Dictionary(true);
			dictionary[target] = true
				
			if (beanInfo)
			{
				if (!target[member])
				{
					objValue = resolveProperty(beanInfo.item, objProperties);
					if (bindable && objProperties.length)
					{
						if (changeWatcher) changeWatcher.unwatch();
						changeWatcher = ChangeWatcher.watch(beanInfo.item, objProperties, bindableHandler, true, true);
					}
						
					try { target[member] = objValue } catch (e:*){return null;}
					if (beanInfo.token != bridge.token)
					{
						bridge.listen(InteractionEvent.DEPENDENCY_REMOVED + itemName, 
							itemRemovedHandler, access, domain);
					}
				}
			}
			else if (lazy)
			{
				bridge.listen(InteractionEvent.DEPENDENCY_ADDED + itemName, 
					dependencyAddedHandler, access, domain);
			}
			
			return objValue;
		}
		
		private function wireProxy(target:Object, proxy:String, member:Object, bridge:IBridge, proxyInfo:Object = null):*
		{
			var objValue:*;
			var objProperties:Array = proxy.split(".");
			itemName = objProperties.shift();
			
			if (!proxyInfo)
			{
				var items:Array = [];
				var evt:InteractionEvent = new InteractionEvent(InteractionEvent.GET_PROXY + itemName);
				evt.data = itemName;
				evt.result = items;
				bridge.dispatch(evt, access, domain);
				proxyInfo = items[0];
			}
			
			dictionary = new Dictionary(true);
			dictionary[target] = true
				
			if (proxyInfo)
			{
				if (!target[member])
				{
					objValue = resolveProperty(proxyInfo.target, objProperties);
					try { target[member] = objValue } catch (e:*){return null;}
					if (proxyInfo.token != bridge.token)
					{
						bridge.listen(InteractionEvent.PROXY_REMOVED + itemName, 
							itemRemovedHandler, access, domain);
					}
				}
			}
			else if (lazy)
			{
				bridge.listen(InteractionEvent.PROXY_ADDED + itemName, 
					proxyAddedHandler, access, domain);
			}
			
			return objValue;
		}
		
		private function resolveProperty(item:Object, properties:Array):*
		{
			var result:* = item;
			
			for (var i:int = 0; i < properties.length; ++i)
			{
				result = result[properties[i]];
			}
			
			return result;
		}
		
		private function itemRemovedHandler(event:Event):void
		{
			bridge.unlisten(event.type, itemRemovedHandler);
			var target:Object = getTarget();
			
			if (target)
			{
				target[member] = null;
				dictionary = null;
			}
		}
		
		private function dependencyAddedHandler(event:Event):void
		{
			bridge.unlisten(InteractionEvent.DEPENDENCY_ADDED + itemName, 
				dependencyAddedHandler);
			
			var target:Object = getTarget();
			
			if (target && event["data"])
			{
				wireBean(target, bean, member, bridge, event["data"]);
			}
		}
		
		private function proxyAddedHandler(event:Event):void
		{
			bridge.unlisten(InteractionEvent.PROXY_ADDED + itemName, 
				proxyAddedHandler);
			
			var target:Object = getTarget();
			
			if (target && event["data"])
			{
				wireProxy(target, proxy, member, bridge, event["data"]);
			}
		}
		
		private function getTarget():Object
		{
			for (var target:Object in dictionary){}
			return target;
		}
		
		private function bindableHandler(event:PropertyChangeEvent):void
		{
			var properties:Array = bean.split(".");
			properties.shift();
			
			var value:* = resolveProperty(event.source, properties);
			try { getTarget()[member] = value } catch (e:*){}
		}
		
	}
}